package Master;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/*
 * The IO class. Provides an implementation of related IO
 * operations in client, like reading task-info, program,
 * input, and resources files.
 */
public class IO {
    /** The upper bound of program size 2MB */
    public static int UPPERBOUNDSIZE = 2 * 1024 * 1024;

    /*
     * Flush data to file on master
     */
    public static void writeFile(String filepath, byte[] file) 
    	throws IOException {
    	System.out.println("filepath: " + filepath);
        try
        {
	        File program = new File(filepath);
	
	        FileOutputStream fileOutputStream = new FileOutputStream(program);
	        fileOutputStream.write(file,0, file.length);
	
	        fileOutputStream.close();
	    }
	    catch(IOException e)
	    {
	    	throw new IOException("'"+filepath+"' is not readable");
	    }
    }

    /*
     * Read data from file on master
     */
    public static byte[] readFile(String filepath) throws IOException{
        byte[] buffer = new byte[IO.UPPERBOUNDSIZE];

        System.out.println("filepath: " + filepath);

        try
        {
	        File programFile = new File(filepath);
	        FileInputStream fileInputStream = new FileInputStream(programFile);
	        int length = fileInputStream.read(buffer);
	        fileInputStream.close();
	
	        byte [] programBody = new byte[length];
	        System.arraycopy(buffer,0, programBody,0,length);
	
	        return programBody;
        }
        catch(IOException e)
        {
        	throw new IOException("'"+filepath+"' is not readable");
        }
    }
    
    /*
     * XML tools
     */
    
    /*
     * Get text value from element
     */
    private static String getTextValue(Element element,String tagName)
    {
		String textVal = null;
		NodeList nl = element.getElementsByTagName(tagName);
		if(nl != null && nl.getLength() > 0) {
			Element el = (Element)nl.item(0);
			textVal = el.getFirstChild().getNodeValue();
		}
		
		return textVal;
    }
    
    /*
     * Get int value from element
     */
    private static int getIntValue(Element element,String tagName)
    {
    	return Integer.parseInt(getTextValue(element,tagName));
    }
    
    /*
     * Get text attribute value from element
     */
    private static String getAttributeTextValue(Element element,
    													String attributeName)
    {
    	return element.getAttribute(attributeName);
    }
    
    /*
     * Get int attribute value from element
     */
    private static int getAttributeIntValue(Element element,String tagName)
    {
    	return Integer.parseInt(getAttributeTextValue(element,tagName));
    }
    
    /*
     * Get list of values from elements with same element names eg <argument>
     */
    private static String[] getStringArray(Element element,String tagName)
    {
    	ArrayList<String> array = new ArrayList<String>();

    	NodeList nl = element.getElementsByTagName(tagName);
    	if(nl != null && nl.getLength() > 0) {
			for(int i=0;i<nl.getLength();i++)
			{
				Element arg_ele = (Element)nl.item(i);
				array.add(arg_ele.getFirstChild().getNodeValue());
			}
		}
    	
    	String[] s_array=new String[array.size()];
    	array.toArray(s_array);
    	return s_array; 
    }
    
    /*
     * Invoke library XML tool to parse XML document
     */
    private static Document parseXMLFile(String filepath) 
    		throws IOException, ParserConfigurationException, SAXException
    {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.parse(new File(filepath));
		doc.getDocumentElement().normalize();
		  
		return doc;
    }
    
    /*
     * Get inner element from outer element
     */
    private static Element getElement(Element element,String tagName)
    {
    	return (Element)element.getElementsByTagName(tagName).item(0);
    }
    
    /*
     * Resources.xml
     */
    public static ArrayList<Resource> readResourceXML(String filepath) 
    		throws Exception
    {
    	Document res_doc = parseXMLFile(filepath);
    	ArrayList<Resource> resource_list= new ArrayList<Resource>();

    	//get the root element
		Element root_ele = res_doc.getDocumentElement();
    	NodeList resource_elements = root_ele.getElementsByTagName("resource");
    	
    	if(resource_elements != null && resource_elements.getLength() > 0) {
	    	for (int s = 0; s < resource_elements.getLength(); s++) {
	    		Element resource_ele = (Element)resource_elements.item(s);
				Resource this_resource = getResource(resource_ele);
				resource_list.add(this_resource);				
	    	}
    	}
    	
    	return resource_list;
    }
    
    /*
     * Initialise an instance of one resource from xml
     */
    private static Resource getResource(Element resource_ele) throws Exception
    {
    	int id=getAttributeIntValue(resource_ele,"id");
    	int port=getIntValue(resource_ele,"socketport");
    	String address=getTextValue(resource_ele,"ipaddress");
    	String hostname=getTextValue(resource_ele,"hostname");
    	String password=getTextValue(resource_ele,"password");
    	float costpersec=Float.parseFloat(getTextValue(resource_ele,
    														"costpersec"));
    	
    	//Debug
    	System.out.printf("R - id: %d, port: %d, address: %s, hostname:%s, "+
    			"password: %s, costpersec:%f\n", 
    			id,port,address,hostname,password,costpersec);
    	
    	return new Resource(id, hostname, address, port, costpersec,password);
    }
    
    /*
     * TaskInfo.xml
     */
    public static ArrayList<Task> readTaskXML(String filepath) throws Exception
    {
    	Document task_doc = parseXMLFile(filepath);
    	ArrayList<Task> task_list= new ArrayList<Task>();

    	Element root_ele = task_doc.getDocumentElement();
    	NodeList task_elements = root_ele.getElementsByTagName("task");
    	
    	if(task_elements != null && task_elements.getLength() > 0) {
	    	for (int s = 0; s < task_elements.getLength(); s++) {
	    		Element task_ele = (Element)task_elements.item(s);
				Task this_task = getTask(task_ele);
				task_list.add(this_task);				
	    	}
    	}
    	
    	return task_list;
    }
    
    /*
     * Initialise an instance of one task from xml
     */
    private static Task getTask(Element task_ele) throws Exception
    {
    	int id=getAttributeIntValue(task_ele,"id");
    	String program_name=getTextValue(getElement(task_ele,"program"),
    																"name");
    	String program_path=getTextValue(getElement(task_ele,"program"),
																	"path");
    	String datafile_name=getTextValue(getElement(task_ele,"data"),
																	"name");
    	String datafile_path=getTextValue(getElement(task_ele,"data"),
    																"path");
    	String outputfile_name=getTextValue(task_ele,"output");
    	String[] arguments=getStringArray(getElement(task_ele,"arguments"),
																"argument");
    	float timeinsec=Float.parseFloat(getTextValue(task_ele,"timeinsec"));
    	
    	//Debug
    	System.out.printf("T - id: %d, pn:%s, pb:%s, dn:%s, dp:%s, o:%s, "+
    			"timeinsec:%f\nArgs: ",
    			id, program_name, program_path, datafile_name, datafile_path,
    			outputfile_name,timeinsec);
    	
    	//Debug as well
    	for(String s:arguments)
    		System.out.print(s+" ");
    	System.out.println();
    	
    	return new Task(id, program_name, program_path,
                arguments, datafile_name, datafile_path, outputfile_name,
                timeinsec);
    }
}
